#region
/* Adoor.NET - Object/Relational mapping framework leveraging ADO.NET`s powerful disconnected mode.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *  
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *  
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
#endregion

using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using Adoor.Expression;
using Adoor.Expression.AST;

namespace Adoor.Relational
{
    public interface IDataSetRelationalStore : IRelationalSource, IDisposable
    {
        DataSet DataSet { get; }
        EntityDataTable GetTable(string tableName, ICollection<DataColumn> dataColumns);
        DataView GetIndex(string tableName, string key);
        EntityRow CreateRow(string tableName, ICollection<DataColumn> dataColumns);
        IDataSetRelationalStore Clone(DataSet cache);
        void FillTable(string tableName, ICollection<DataColumn> dataColumns, DataView view);
    }

    public class DataSetRelationalStore : IDataSetRelationalStore, IDisposable
    {
        //protected static log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        private DataSet dataSet;

        //public DataSet DataSet { get { return this.dataSet; } }
        public DataSet DataSet{ get { return this.dataSet; } }

        public DataSetRelationalStore(DataSet dataSet)
        {
            this.dataSet = dataSet;
        }

        public DataSetRelationalStore()
            : this(new DataSet())
        {
        }

        public event QueryEventHandler OnQuery;

        public void FillTable(string tableName, ICollection<DataColumn> dataColumns, DataView view)
        {
            DataTable table = this.GetTable(tableName, dataColumns);
            // Update the table
            StringBuilder sb = new StringBuilder();

            for (int i = 0; i < table.PrimaryKey.Length; i++)
            {
                sb.Append(table.PrimaryKey[i].ColumnName);
                if (i != (table.PrimaryKey.Length - 1))
                    sb.Append(", ");
            }

            DataView dv = new DataView(table);
            try
            {
                dv.Sort = sb.ToString();
                dv.RowStateFilter = DataViewRowState.CurrentRows | DataViewRowState.Deleted;

                // TODO: Table.BeginLoadData();

                foreach (DataRowView drv in view)
                {
                    bool keyIsNull = false;
                    object[] objs = new object[table.PrimaryKey.Length];
                    for (int i = 0; i < table.PrimaryKey.Length; i++)
                    {
                        string columnName = table.PrimaryKey[i].ColumnName;
                        if (drv.Row.IsNull(columnName))
                        {
                            keyIsNull = true;
                            break;
                        }
                        objs[i] = drv[columnName];
                        // TODO: Bad guid handling, should map db datatype better
                        if (table.PrimaryKey[i].DataType == typeof(String) && !(objs[i] is string))
                        {
                            objs[i] = objs[i].ToString();
                        }
                    }

                    if ((!keyIsNull) && dv.Find(objs) == -1) // if neither in CurrentRows nor in Deleted
                    {
                        table.ImportRow(drv.Row);
                        //					DataRow dr = table.NewRow();
                        //					foreach (DataColumn dc in table.Columns)
                        //						dr[dc.ColumnName] = drv[dc.ColumnName];
                        //					table.Rows.Add(dr);
                    }
                }
#if DEBUG2
				Debug.Assert(table.DefaultView.Count == table.Rows.Count);
#endif
            }
            finally
            {
                dv.Table = null; // Do not keep too many DataViews on the DataTable
            }
        }

        public void ExecuteDelete(RelationalOperator op, System.Collections.ICollection args)
        {
            // TODO:
            throw new NotImplementedException();
        }

        public void ExecuteUpdate(RelationalQuery query, System.Collections.ICollection args)
        {
            if (this.OnQuery != null)
            {
                QueryEventArgs qeargs = new QueryEventArgs(query, ExpressionKind.Aggregate);
                this.OnQuery(this, qeargs);
                query = qeargs.Query;
            }
            // TODO:
            throw new NotImplementedException();
        }

        public IDataReader ExecuteReader(RelationalQuery query, System.Collections.ICollection args)
        {
            if (this.OnQuery != null)
            {
                QueryEventArgs qeargs = new QueryEventArgs(query, ExpressionKind.Aggregate);
                this.OnQuery(this, qeargs);
                query = qeargs.Query;
            }
            // TODO:
            throw new NotImplementedException();
        }

        public Int64 ExecuteCount(RelationalQuery query, System.Collections.ICollection args)
        {
            if (this.OnQuery != null)
            {
                QueryEventArgs qeargs = new QueryEventArgs(query, ExpressionKind.Aggregate);
                this.OnQuery(this, qeargs);
                query = qeargs.Query;
            }
            DataTable table = Adoor.Data.DataUtils.CreateCaseInsesitiveDataTable();
            Fill(query, args, table);
            return (Int64)table.Rows.Count;
        }

        public EntityRow CreateRow(string tableName, ICollection<DataColumn> dataColumns)
        {
            EntityDataTable table = GetTable(tableName, dataColumns);
            EntityRow res = (EntityRow)table.NewRow();
            if (table.DataSet != null)
                table.DataSet.EnforceConstraints = false;
            table.Rows.Add(res);
            return res;
        }

        public EntityDataTable GetTable(string tableName, ICollection<DataColumn> dataColumns)
        {
            EntityDataTable result = (EntityDataTable)this.DataSet.Tables[tableName];
            if (result == null)
            {
                if (dataColumns != null)
                {
                    result = new EntityDataTable();
                    result.TableName = tableName;
                    this.DataSet.Tables.Add(result);
                    foreach (DataColumn o in dataColumns)
                    {
                        DataColumn dc = (DataColumn)o;
                        DataColumn newDc = result.Columns.Add();
                        newDc.AllowDBNull = dc.AllowDBNull;
                        newDc.Caption = dc.Caption;
                        newDc.ColumnName = dc.ColumnName;
                        newDc.DataType = dc.DataType;
                        newDc.DefaultValue = dc.DefaultValue;
                        newDc.Expression = dc.Expression;
                        newDc.MaxLength = dc.MaxLength;
                        newDc.Unique = dc.Unique;
                        if (newDc.Unique)
                            result.PrimaryKey = new DataColumn[] { newDc };
                    }
                    // TODO: change this default rule
                    if (result.PrimaryKey.Length == 0)
                    {
                        DataColumn[] primaryKeys = new DataColumn[result.Columns.Count];
                        foreach (DataColumn dc in result.Columns)
                            primaryKeys[dc.Ordinal] = dc;
                        result.PrimaryKey = primaryKeys;
                    }
                }
                //#if DEBUG2
                //				result.RowChanging += new DataRowChangeEventHandler(result_RowChanging);
                //				result.RowChanged += new DataRowChangeEventHandler(result_RowChanged);
                //#endif
            }
            return result;
        }

        //#if DEBUG2
        //		private void result_RowChanging(object sender, DataRowChangeEventArgs e)
        //		{
        //			
        //		}
        //
        //		private void result_RowChanged(object sender, DataRowChangeEventArgs e)
        //		{			
        //			if(e.Row.Table.TableName == "Tarif")
        //			{
        //				Trace.WriteLine(" Tarif rowchange " +  e.Row[4].ToString());
        //				if(e.Row[4].ToString() == "1")
        //				{
        //					string s = "";
        //				}
        //			}
        //		}
        //#endif

        public DataView GetIndex(string tableName, string key)
        {
            DataTable table = this.DataSet.Tables[tableName];
            if (table == null)
                return null;
            DataView result = (DataView)table.ExtendedProperties[key];
            if (result == null)
            {
                result = new DataView(table);
                result.RowStateFilter |= DataViewRowState.Deleted;
                //result.Table = table;
                result.Sort = key;
                table.ExtendedProperties[key] = result;
            }
            
            return result;
        }

        public void Fill(RelationalQuery query, System.Collections.ICollection args, DataSet result)
        {
            DataTable table = Adoor.Data.DataUtils.CreateCaseInsesitiveDataTable();
            result.Tables.Add(table);
            Fill(query, args, table);
        }

        public void Fill(RelationalQuery query, System.Collections.ICollection args, DataSet result, string tableName)
        {
            DataTable table = result.Tables[tableName];
            if (table == null)
            {
                table = new DataTable(tableName);
                result.Tables.Add(table);
            }
            result.Tables.Add(table);
            Fill(query, args, table);
        }

        public void Fill(RelationalQuery query, IDictionary<string, object> args, DataTable result)
        {
            if (this.OnQuery != null)
            {
                QueryEventArgs qeargs = new QueryEventArgs(query, ExpressionKind.Aggregate);
                this.OnQuery(this, qeargs);
                query = qeargs.Query;
            }

            DataSetEvaluator.ExecuteDataSet(this, query, args, result);
        }

        public void Fill(RelationalQuery query, object args, DataTable result)
        {
            if (args is IDictionary<string, object>)
                Fill(query, (IDictionary<string, object>)args, result);
            else if (args is System.Collections.ICollection)
                Fill(query, (System.Collections.ICollection)args, result);
            else
                throw new NotSupportedException();
        }

        public void Fill(RelationalQuery query, System.Collections.ICollection args, DataTable result)
        {
            if (this.OnQuery != null)
            {
                QueryEventArgs qeargs = new QueryEventArgs(query, ExpressionKind.Aggregate);
                this.OnQuery(this, qeargs);
                query = qeargs.Query;
            }

            DataSetEvaluator.ExecuteDataSet(this, query, args, result);
        }

        public object GetScalar(RelationalQuery query, System.Collections.ICollection args)
        {
            DataTable tempTable = Adoor.Data.DataUtils.CreateCaseInsesitiveDataTable();
            Fill(query, args, tempTable);
            if (tempTable.Rows.Count == 0)
                return null;
            return tempTable.Rows[0][0];
        }

        public void ExecuteNonQuery(RelationalQuery query, System.Collections.ICollection args)
        {
            throw new NotImplementedException();
        }

        public void ExecuteNonQuery(RelationalQuery query, IDictionary<string, object> args)
        {
            throw new NotImplementedException();
        }

        private class DataSetEvaluator : IRelationalOperatorEvaluator, IRelationalQueryEvaluator
        {
            public static void ExecuteDataSet(IDataSetRelationalStore context, RelationalQuery query, System.Collections.ICollection args, DataTable result)
            {
                
                if (query.Distinct)
                    throw new NotImplementedException("distinct not implemented on local store");
                DataSetEvaluator evaluator = new DataSetEvaluator(result, context, args);
                query.EvaluateFrom(evaluator);
                UnqualifyColumnNames(evaluator.result);  // useful ?
                if (query.TopCount != Int64.MaxValue)
                {
                    for (int i = result.Rows.Count - 1; i >= (int)query.TopCount; i--)
                        result.Rows[i].Delete();
                    result.AcceptChanges();
                }
            }

            public static void ExecuteDataSet(IDataSetRelationalStore context, RelationalQuery query, IDictionary<string, object> args, DataTable result)
            {
                
                if (query.Distinct)
                    throw new NotImplementedException("distinct not implemented on local store");
                DataSetEvaluator evaluator = new DataSetEvaluator(result, context, args);
                query.EvaluateFrom(evaluator);
                UnqualifyColumnNames(evaluator.result);  // useful ?
                if (query.TopCount != Int64.MaxValue)
                {
                    for (int i = result.Rows.Count - 1; i >= (int)query.TopCount; i--)
                        result.Rows[i].Delete();
                    result.AcceptChanges();
                }
            }

            protected System.Collections.IEnumerator args;
            protected IDictionary<string, object> dicargs;

            protected DataSetEvaluator(DataTable result, IDataSetRelationalStore context, System.Collections.ICollection args)
            {
                this.args = args.GetEnumerator();
                this.context = context;
                this.result = result;
            }

            protected DataSetEvaluator(DataTable result, IDataSetRelationalStore context, IDictionary<string, object> args)
            {
                this.dicargs = args;
                this.context = context;
                this.result = result;
            }

            const string ParamPlaceHolder = "?";

            class DataSetHelper
            {
                public DataSetHelper()
                {

                }

                //				private class FieldInfo
                //				{
                //					public string RelationName;
                //					public string FieldName;	//source table field name
                //					public string FieldAlias;	//destination table field name
                //					public string Aggregate;
                //				}

                //private System.Collections.ArrayList m_FieldInfo;
                //private System.Collections.ArrayList GroupByFieldInfo; 

                //		private void ParseFieldList(string FieldList, bool AllowRelation)
                //		{
                //			/*
                //			 * This code parses FieldList into FieldInfo objects  and then
                //			 * adds them to the m_FieldInfo private member
                //			 *
                //			 * FieldList systax:  [relationname.]fieldname[ alias], ...
                //			*/
                //			if (m_FieldList == FieldList) return;
                //			m_FieldInfo = new System.Collections.ArrayList();
                //			m_FieldList = FieldList;
                //			FieldInfo Field; string[] FieldParts; string[] Fields=FieldList.Split(',');
                //			int i;
                //			for (i=0; i<=Fields.Length-1; i++)
                //			{
                //				Field=new FieldInfo();
                //				//parse FieldAlias
                //				FieldParts = Fields[i].Trim().Split(' ');
                //				switch (FieldParts.Length)
                //				{
                //					case 1:
                //						//to be set at the end of the loop
                //						break;
                //					case 2:
                //						Field.FieldAlias=FieldParts[1];
                //						break;
                //					default:
                //						throw new Exception("Too many spaces in field definition: '" + Fields[i] + "'.");
                //				}
                //				//parse FieldName and RelationName
                //				FieldParts = FieldParts[0].Split('.');
                //				switch (FieldParts.Length)
                //				{
                //					case 1:
                //						Field.FieldName=FieldParts[0];
                //						break;
                //					case 2:
                //						if (AllowRelation==false)
                //							throw new Exception("Relation specifiers not permitted in field list: '" + Fields[i] + "'.");
                //						Field.RelationName = FieldParts[0].Trim();
                //						Field.FieldName=FieldParts[1].Trim();
                //						break;
                //					default:
                //						throw new Exception("Invalid field definition: " + Fields[i] + "'.");
                //				}
                //				if (Field.FieldAlias==null)
                //					Field.FieldAlias = Field.FieldName;
                //				m_FieldInfo.Add (Field);
                //			}
                //		}
                //
                //		private void ParseGroupByFieldList(string FieldList)
                //		{
                //			/*
                //			* Parses FieldList into FieldInfo objects and adds them to the GroupByFieldInfo private member
                //			*
                //			* FieldList syntax: fieldname[ alias]|operatorname(fieldname)[ alias],...
                //			*
                //			* Supported Operators: count,sum,max,min,first,last
                //			*/
                //			if (GroupByFieldList == FieldList) return;
                //			GroupByFieldInfo = new System.Collections.ArrayList();
                //			FieldInfo Field; string[] FieldParts; string[] Fields = FieldList.Split(',');
                //			for (int i=0; i<=Fields.Length-1;i++)
                //			{
                //				Field = new FieldInfo();
                //				//Parse FieldAlias
                //				FieldParts = Fields[i].Trim().Split(' ');
                //				switch (FieldParts.Length)
                //				{
                //					case 1:
                //						//to be set at the end of the loop
                //						break;
                //					case 2:
                //						Field.FieldAlias = FieldParts[1];
                //						break;
                //					default:
                //						throw new ArgumentException("Too many spaces in field definition: '" + Fields[i] + "'.");
                //				}
                //				//Parse FieldName and Aggregate
                //				FieldParts = FieldParts[0].Split('(');
                //				switch (FieldParts.Length)
                //				{
                //					case 1:
                //						Field.FieldName = FieldParts[0];
                //						break;
                //					case 2:
                //						Field.Aggregate = FieldParts[0].Trim().ToLower();    //we're doing a case-sensitive comparison later
                //						Field.FieldName = FieldParts[1].Trim(' ', ')');
                //						break;
                //					default:
                //						throw new ArgumentException("Invalid field definition: '" + Fields[i] + "'.");
                //				}
                //				if (Field.FieldAlias==null)
                //				{
                //					if (Field.Aggregate==null)
                //						Field.FieldAlias=Field.FieldName;
                //					else
                //						Field.FieldAlias = Field.Aggregate + "of" + Field.FieldName;
                //				}
                //				GroupByFieldInfo.Add(Field);
                //			}
                //			GroupByFieldList = FieldList;
                //		}

                public DataTable CreateGroupByTable(string TableName, DataTable SourceTable
                    , SelectExpression[] FieldList)
                {
                    /*
                     * Creates a table based on aggregates of fields of another table
                     *
                     * RowFilter affects rows before GroupBy operation. No "Having" support
                     * though this can be emulated by subsequent filtering of the table that results
                     *
                     *  FieldList syntax: fieldname[ alias]|aggregatefunction(fieldname)[ alias], ...
                    */
                    if (FieldList == null || FieldList.Length == 0)
                    {
                        throw new ArgumentException("You must specify at least one field in the field list.");
                        //return CreateTable(TableName, SourceTable);
                    }
                    else
                    {
                        DataTable dt = Adoor.Data.DataUtils.CreateCaseInsesitiveDataTable();
                        //ParseGroupByFieldList(FieldList);

                        foreach (SelectExpression sel in FieldList)
                        {
                            addColumn(SourceTable, dt, sel.Expr);
                        }

                        return dt;
                    }
                }

                private void addColumn(DataTable source, DataTable dt, Adoor.Expression.AST.ExpressionNode node)
                {
                    if (node is ExpressionFunction)
                    {
                        ExpressionFunction ef = (ExpressionFunction)node;
                        foreach (ExpressionNode n in ef.Parameters)
                            addColumn(source, dt, n);
                    }
                    else
                    {
                        string colName = FilterStringizer.Evaluate(node);
                        DataColumn dc = source.Columns[colName];
                        DataColumn dcDest = dt.Columns[colName];
                        if (dcDest == null)
                            dt.Columns.Add(dc.ColumnName, dc.DataType);
                    }
                }

                public void InsertGroupByInto(DataTable DestTable, DataTable SourceTable
                    , SelectExpression[] FieldList,
                    Adoor.Expression.AST.HavingExpression RowFilter, Adoor.Expression.AST.GroupByExpression[] GroupBy)
                {
                    /*
                     * Copies the selected rows and columns from SourceTable and inserts them into DestTable
                     * FieldList has same format as CreateGroupByTable
                    */
                    if (FieldList == null || FieldList.Length == 0)
                        throw new ArgumentException("You must specify at least one field in the field list.");
                    //ParseGroupByFieldList(FieldList);	//parse field list
                    //ParseFieldList(GroupBy,false);			//parse field names to Group By into an arraylist
                    string rowFilter = RowFilter == null ? string.Empty : FilterStringizer.Evaluate(RowFilter.Expr);
                    StringBuilder gbyFields = new StringBuilder();
                    bool needComma = false;
                    List<string> colNames = new List<string>();
                    foreach (GroupByExpression gbe in GroupBy)
                    {
                        if (needComma)
                            gbyFields.Append(",");
                        needComma = true;
                        string colName = FilterStringizer.Evaluate(gbe.Expr);
                        colNames.Add(colName);
                        gbyFields.Append(colName);
                    }

                    DataRow[] Rows = SourceTable.Select(rowFilter, gbyFields.ToString());
                    DataRow LastSourceRow = null, DestRow = null; bool SameRow; int RowCount = 0;
                    foreach (DataRow SourceRow in Rows)
                    {
                        SameRow = false;
                        if (LastSourceRow != null)
                        {
                            SameRow = true;
                            foreach (string s in colNames)
                            {
                                if (!ColumnEqual(LastSourceRow[s], SourceRow[s]))
                                {
                                    SameRow = false;
                                    break;
                                }
                            }
                            if (!SameRow)
                                DestTable.Rows.Add(DestRow);
                        }
                        if (!SameRow)
                        {
                            DestRow = DestTable.NewRow();
                            RowCount = 0;
                        }
                        RowCount += 1;
                        foreach (SelectExpression s in FieldList)
                        {
                            if (s.Expr is ExpressionFunction)
                            {
                                ExpressionFunction ef = (ExpressionFunction)s.Expr;
                                switch (ef.Name.ToLower())
                                {
                                    case "count":
                                        {
                                            DestRow[s.Alias] = RowCount;

                                        } break;
                                    case "sum":
                                        {
                                            string col = FilterStringizer.Evaluate(ef.Parameters[0]);
                                            DestRow[s.Alias] = Add(DestRow[s.Alias], SourceRow[col]);
                                        } break;
                                    case "max":
                                        {
                                            string col = FilterStringizer.Evaluate(ef.Parameters[0]);
                                            DestRow[s.Alias] = Max(DestRow[s.Alias], SourceRow[col]);
                                        } break;
                                    case "min":
                                        {
                                            string col = FilterStringizer.Evaluate(ef.Parameters[0]);
                                            if (RowCount == 1)
                                                DestRow[s.Alias] = SourceRow[col];
                                            else
                                                DestRow[s.Alias] = Min(DestRow[s.Alias], SourceRow[col]);
                                            break;
                                        }
                                }
                            }
                            else
                            {
                                string colName = FilterStringizer.Evaluate(s.Expr);
                                DestRow[colName] = SourceRow[colName];
                            }
                        }
                        //						foreach(FieldInfo Field in GroupByFieldInfo)
                        //						{
                        //							switch(Field.Aggregate)    //this test is case-sensitive
                        //							{
                        //								case null:        //implicit last
                        //								case "":        //implicit last
                        //								case "last":
                        //									DestRow[Field.FieldAlias]=SourceRow[Field.FieldName];
                        //									break;
                        //								case "first":
                        //									if (RowCount==1)
                        //										DestRow[Field.FieldAlias]=SourceRow[Field.FieldName];
                        //									break;
                        //								case "count":
                        //									DestRow[Field.FieldAlias]=RowCount;
                        //									break;
                        //								case "sum":
                        //									DestRow[Field.FieldAlias]=Add(DestRow[Field.FieldAlias], SourceRow[Field.FieldName]);
                        //									break;
                        //								case "max":
                        //									DestRow[Field.FieldAlias]=Max(DestRow[Field.FieldAlias], SourceRow[Field.FieldName]);
                        //									break;
                        //								case "min":
                        //									if (RowCount==1)
                        //										DestRow[Field.FieldAlias]=SourceRow[Field.FieldName];
                        //									else
                        //										DestRow[Field.FieldAlias]=Min(DestRow[Field.FieldAlias], SourceRow[Field.FieldName]);
                        //									break;
                        //							}
                        //						}
                        LastSourceRow = SourceRow;
                    }
                    if (DestRow != null)
                        DestTable.Rows.Add(DestRow);
                }


                //				private FieldInfo LocateFieldInfoByName(System.Collections.ArrayList FieldList, string Name)
                //				{
                //					//Looks up a FieldInfo record based on FieldName
                //					foreach (FieldInfo Field in FieldList)
                //					{
                //						if (Field.FieldName==Name)
                //							return Field;
                //					}
                //					return null;
                //				}

                private bool ColumnEqual(object a, object b)
                {
                    /*
                     * Compares two values to see if they are equal. Also compares DBNULL.Value.
                     *
                     * Note: If your DataTable contains object fields, you must extend this
                     * function to handle them in a meaningful way if you intend to group on them.
                    */
                    if ((a is DBNull) && (b is DBNull))
                        return true;    //both are null
                    if ((a is DBNull) || (b is DBNull))
                        return false;    //only one is null
                    bool colEq = object.Equals(a, b);    //value type standard comparison
                    return colEq;
                }

                private object Min(object a, object b)
                {
                    //Returns MIN of two values - DBNull is less than all others
                    if ((a is DBNull) || (b is DBNull))
                        return DBNull.Value;
                    if (((IComparable)a).CompareTo(b) == -1)
                        return a;
                    else
                        return b;
                }

                private object Max(object a, object b)
                {
                    //Returns Max of two values - DBNull is less than all others
                    if (a is DBNull)
                        return b;
                    if (b is DBNull)
                        return a;
                    if (((IComparable)a).CompareTo(b) == 1)
                        return a;
                    else
                        return b;
                }

                private object Add(object a, object b)
                {
                    //Adds two values - if one is DBNull, then returns the other
                    if (a is DBNull)
                        return b;
                    if (b is DBNull)
                        return a;
                    return ((decimal)a + (decimal)b);
                }

                public DataTable SelectGroupByInto(string TableName, DataTable SourceTable, SelectExpression[] FieldList,
                    Adoor.Expression.AST.HavingExpression RowFilter, Adoor.Expression.AST.GroupByExpression[] GroupBy)
                {
                    /*
                     * Selects data from one DataTable to another and performs various aggregate functions
                     * along the way. See InsertGroupByInto and ParseGroupByFieldList for supported aggregate functions.
                     */
                    DataTable dt = CreateGroupByTable(TableName, SourceTable, FieldList);
                    InsertGroupByInto(dt, SourceTable, FieldList, RowFilter, GroupBy);
                    return dt;
                }

                public void CreateColumns(DataTable SourceTable, DataTable DestTable, SelectExpression[] FieldList)
                {
                    /*
                     * Selects data from one DataTable to another and performs various aggregate functions
                     * along the way. See InsertGroupByInto and ParseGroupByFieldList for supported aggregate functions.
                     */
                    foreach (SelectExpression sel in FieldList)
                    {
                        ExpressionNode n = sel.Expr;
                        string colName;
                        Type typeCol = typeof(object);
                        DataColumn dc = null;
                        if (n is ExpressionFunction)
                        {
                            colName = sel.Alias;
                            if (((ExpressionFunction)n).Name.ToLower() == "count")
                                typeCol = typeof(int);
                            else
                            {
                                ExpressionName anp1 = (ExpressionName)((ExpressionFunction)n).Parameters[0];
                                colName = FilterStringizer.Evaluate(anp1);
                                DataColumn dcInner = SourceTable.Columns[colName];
                                if (dcInner != null)
                                {
                                    typeCol = dcInner.DataType;
                                    dc = new DataColumn(colName, typeCol);
                                }
                            }
                        }
                        else
                        {
                            colName = FilterStringizer.Evaluate(sel.Expr);
                            dc = SourceTable.Columns[colName];
                        }

                        DataColumn dcDest = DestTable.Columns[colName];
                        if (dc == null)
                        {
                            dc = new DataColumn(colName, typeCol);
                        }
                        if (dcDest == null)
                            DestTable.Columns.Add(dc.ColumnName, dc.DataType);
                    }
                }
            }

            class FilterStringizer : ExpressionStringizer
            {
                public static string Evaluate(ExpressionNode n, System.Collections.IEnumerator args, IDictionary<string, object> dicArgs, bool invariantCulture)
                {
                    FilterStringizer es = new FilterStringizer(args, dicArgs, invariantCulture);
                    n.Accept(es);
                    return es.Result.ToString();
                }

                private bool invariantCulture;

                System.Collections.IEnumerator args;
                IDictionary<string, object> dicArgs;
                public FilterStringizer(System.Collections.IEnumerator args, IDictionary<string, object> dicArgs, bool invariantCulture)
                {
                    this.args = args;
                    this.dicArgs = dicArgs;
                    this.invariantCulture = invariantCulture;
                }

                public override void VisitPrimary(ExpressionPrimary p)
                {
                    if (p.Type == ScannerToken.Params)
                    {
                        if (dicArgs == null && this.args != null)
                        {
                            if (args.MoveNext())
                            {
                                dicArgs = args.Current as IDictionary<string, object>;
                            }
                        }

                        object o = this.dicArgs[p.Value];
                        string v = getStringArgs(o);
                        this.Result.Append(v);
                    }
                    else if (p.Value == ParamPlaceHolder)
                    {
                        if (!this.args.MoveNext())
                            throw new Exception("too few arguments");

                        object o = this.args.Current;
                        string v = getStringArgs(o);
                        this.Result.Append(v);
                    }
                    else
                        base.VisitPrimary(p);
                }

                private string getStringArgs(object o)
                {
                    object[] key = o as object[];
                    string v = string.Empty;

                    //ci.DateTimeFormat.LongTimePattern = ci.DateTimeFormat.LongTimePattern + ":fff";
                    if (key != null)
                    {
                        bool needComma = false;
                        foreach (object item in key)
                        {
                            if (needComma)
                                v += ", ";

                            v += getStringFromPrimary(item);
                            needComma = true;
                        }
                        //v = Convert.ToString(key[0], CultureInfo.InvariantCulture);							
                    }
                    else
                    {
                        v = getStringFromPrimary(o);
                    }
                    return v;
                }

                private string getStringFromPrimary(object o)
                {
                    string v;


                    CultureInfo ci = null;
                    if (invariantCulture)
                    {
                        ci = new CultureInfo(CultureInfo.InvariantCulture.Name);
                    }
                    else
                        ci = (CultureInfo)System.Threading.Thread.CurrentThread.CurrentUICulture.Clone();

                    ci.NumberFormat.NumberGroupSeparator = "";
                    ci.NumberFormat.CurrencyGroupSeparator = "";
                    ci.NumberFormat.CurrencySymbol = "";

                    Type type = o.GetType();

                    if (o is DateTime)
                    {
                        DateTime dt = (DateTime)o;
                        v = dt.ToString(ci);
                    }
                    else if (o is Guid)
                    {
                        v = "Convert('" + o.ToString() + "', 'System.Guid')";
                    }
                    else if (type.IsEnum)
                    {
                        v = Convert.ToString((int)o);
                    }
                    else
                    {
                        //v = Convert.ToString(o, CultureInfo.InvariantCulture);
                        v = Convert.ToString(o, ci);
                    }

                    if (o is string || o is DateTime || o is Single || o is Double || o is Decimal)
                        v = "'" + v.Replace("'", "''") + "'";
                    return v;
                }
            }

            void IRelationalOperatorEvaluator.EvaluateFilter(FilterOperator f)
            {
                f.Source.EvaluateFrom(this);

                try
                {
                    DataRow[] good = new DataRow[] { };

                    string filter = string.Empty;
                    try
                    {
                        filter = FilterStringizer.Evaluate(f.Condition, this.args, this.dicargs, false);
                        good = result.Select(filter);
                    }
                    catch (Exception ex)
                    {
                        AdoorLoger.Debug("DataSetEvaluator.EvaluateFilter: filter = " + filter + " exception trying switching to invariant culture " + ex.ToString());
                        
                        filter = FilterStringizer.Evaluate(f.Condition, this.args, this.dicargs, true);
                        good = result.Select(filter);
                    }

                    //object obj = typeof(DataRow).GetProperty("RowID", System.Reflection.BindingFlags.NonPublic).GetGetMethod().Invoke(good[0], null);    

                    AdoorLoger.Debug("DataSetEvaluator.EvaluateFilter: filter = " + filter);
                    

                    List<DataRow> work = new List<DataRow>();

                    foreach (DataRow item in result.Rows)
                        work.Add(item);

                    foreach (DataRow goodRow in good)
                        work.Remove(goodRow);

                    for (int i = work.Count - 1; i >= 0; i--)
                        (work[i]).Delete();

                    //foreach (DataRow goodRow in good)
                    //{
                    //    for (int i = result.Rows.Count - 1; i >= 0; i--)
                    //    {
                    //        if (goodRow != result.Rows[i])
                    //        {
                    //            if (log.IsDebugEnabled)
                    //                log.Debug("DataSetEvaluator.EvaluateFilter: Row.Delete");
                    //            result.Rows[i].Delete();
                    //        }
                    //    }
                    //}
                    //foreach (DataRow gooodRow in good)
                    //{

                    //    result.Rows.Add(gooodRow);
                    //}


                    this.result.AcceptChanges();

                    AdoorLoger.Debug("Filter: count = " + this.result.Rows.Count);

                }
                catch
                {
                    throw;
                }
                //finally
                //{
                //    dv.Table = null; // bug in ADO.NET when several DataViews on one DataTable, so disconnect this DataView
                //}


            }
            //void IRelationalOperatorEvaluator.EvaluateFilter(FilterOperator f)
            //{
            //    f.Source.EvaluateFrom(this);				

            //    DataView dv = new DataView(this.result);
            //    try
            //    {
            //        string filter = string.Empty;
            //        try
            //        {
            //            filter = FilterStringizer.Evaluate(f.Condition, this.args, this.dicargs, false);
            //            dv.RowFilter = "not(" + filter + ")"; // TODO: this line can be VERY expensive
            //        }
            //        catch(Exception ex)
            //        {
            //            if(log.IsDebugEnabled)
            //            {
            //                log.Debug("DataSetEvaluator.EvaluateFilter: filter = " + filter + " exception trying switching to invariant culture " + ex.ToString());
            //            }
            //            filter = FilterStringizer.Evaluate(f.Condition, this.args, this.dicargs, true);
            //            dv.RowFilter = "not(" + filter + ")"; // TODO: this line can be VERY expensive
            //        }

            //        if(log.IsDebugEnabled)
            //        {
            //            log.Debug("DataSetEvaluator.EvaluateFilter: filter = " + filter);
            //        }

            //        if (dv.Count > 0)
            //        {
            //            for (int i = dv.Count - 1; i >= 0; i--)
            //            {
            //                if (log.IsDebugEnabled)
            //                    log.Debug("DataSetEvaluator.EvaluateFilter: Row.Delete");
            //                dv[i].Row.Delete();
            //            }
            //        }

            //        //foreach(DataRowView drv in dv)
            //        //{
            //        //    if(log.IsDebugEnabled)
            //        //        log.Debug("DataSetEvaluator.EvaluateFilter: Row.Delete");
            //        //    drv.Row.Delete();
            //        //}
            //        this.result.AcceptChanges();

            //        if(log.IsDebugEnabled)
            //            log.Debug("Filter: count = " + this.result.Rows.Count);

            //    }
            //    finally 
            //    {
            //        dv.Table = null; // bug in ADO.NET when several DataViews on one DataTable, so disconnect this DataView
            //    }
            //}

            void IRelationalOperatorEvaluator.EvaluateGroupBy(GroupByOperator g)
            {
                DataTable finalResult = this.result;
                DataTable innerResult = Adoor.Data.DataUtils.CreateCaseInsesitiveDataTable();
                this.result = innerResult;
                g.Source.EvaluateFrom(this);
                this.result = finalResult;

                DataSetHelper dsh = new DataSetHelper();
                dsh.CreateColumns(innerResult, finalResult, g.SelectColumns);
                dsh.InsertGroupByInto(finalResult, innerResult, g.SelectColumns, g.Having, g.GroupByColumns);
                //this.result = dsh.SelectGroupByInto(this.result.TableName, this.result, g.SelectColumns, g.Having, g.GroupByColumns);
            }

            private static void UnqualifyColumnNames(DataTable table)
            {
                foreach (DataColumn dc in table.Columns)
                {
                    int pos = dc.ColumnName.IndexOf(".");
                    if (pos != -1)
                        dc.ColumnName = dc.ColumnName.Substring(pos + 1);
                }
            }

            void IRelationalQueryEvaluator.EvaluateInsert(InsertQuery i)
            {
                EvaluateInsert(i);
            }

            protected virtual void EvaluateInsert(InsertQuery i)
            {
                throw new NotImplementedException();
            }

            void IRelationalQueryEvaluator.EvaluateUpdate(UpdateQuery u)
            {
                EvaluateUpdate(u);
            }

            protected virtual void EvaluateUpdate(UpdateQuery u)
            {
                throw new NotImplementedException();
            }

            void IRelationalQueryEvaluator.EvaluateDelete(DeleteQuery d)
            {
                EvaluateDelete(d);
            }

            protected virtual void EvaluateDelete(DeleteQuery i)
            {
                throw new NotImplementedException();
            }

            void IRelationalOperatorEvaluator.EvaluateJoin(JoinOperator j)
            {
                // because Parent.Name would be interpreted as a relation by DataView.RowFilter
                // we exclude "Parent" as an allowed correlation name
                if (string.Compare(j.Alias, "Parent", true) == 0)
                    throw new Exception("internal restriction : " + j.Alias + " is an invalid correlation name");

                DataTable oldResult = this.result;
                this.result = Adoor.Data.DataUtils.CreateCaseInsesitiveDataTable();
                j.Source1.EvaluateFrom(this);


                DataView source1 = new DataView(this.result);

                this.result = Adoor.Data.DataUtils.CreateCaseInsesitiveDataTable();
                j.Source2.EvaluateFrom(this);

                foreach (DataColumn dc in this.result.Columns)
                    dc.ColumnName = j.Alias + "." + dc.ColumnName;

                DataView source2 = new DataView(this.result);

                this.result = oldResult;
                this.result.Columns.Clear();
                foreach (DataColumn dc in source1.Table.Columns)
                    this.result.Columns.Add(dc.ColumnName, dc.DataType);
                foreach (DataColumn dc in source2.Table.Columns)
                    this.result.Columns.Add(dc.ColumnName, dc.DataType);
                source2.Sort = j.Alias + "." + j.Column2;
                foreach (DataRowView drv in source1)
                {
                    string columnName1 = j.Column1;
                    columnName1 = j.Correlation + "." + columnName1;
                    int index = source2.Find(drv[columnName1]);
                    if (index != -1)
                    {
                        // "Find" does not find the first row (it must perform a dichotomy)
                        // so step back to the first one
                        while (index > 0 && drv[columnName1].Equals(source2[index - 1][j.Alias + "." + j.Column2]))
                            --index;

                        do
                        {
                            DataRow dr = this.result.NewRow();
                            foreach (DataColumn dc in source1.Table.Columns)
                                dr[dc.ColumnName] = drv[dc.ColumnName];
                            foreach (DataColumn dc in source2.Table.Columns)
                                dr[dc.ColumnName] = source2[index][dc.ColumnName];
                            this.result.Rows.Add(dr);
                            ++index;
                        }
                        while (index < source2.Count && drv[columnName1].Equals(source2[index][j.Alias + "." + j.Column2]));
                    }
                    else if (j.IsOuterJoin)
                    {
                        DataRow dr = this.result.NewRow();
                        foreach (DataColumn dc in source1.Table.Columns)
                        {
                            string columnName = dc.ColumnName;
                            dr[columnName] = drv[dc.ColumnName];
                        }
                        this.result.Rows.Add(dr);
                    }
                }
                AdoorLoger.Debug("Join: count = " + this.result.Rows.Count);
            }

            void IRelationalOperatorEvaluator.EvaluateCrossQuery(CrossQueryOperator c)
            {
                throw new NotImplementedException();
            }

            void IRelationalOperatorEvaluator.EvaluateScanQueryOperator(ScanQueryOperator s)
            {
                throw new NotImplementedException();
            }

            void IRelationalOperatorEvaluator.EvaluateCross(CrossOperator c)
            {
                throw new NotImplementedException();
            }

            void IRelationalOperatorEvaluator.EvaluateAlias(AliasOperator a)
            {
                // because Parent.Name would be interpreted as a relation by DataView.RowFilter
                // we exclude "Parent" as an allowed correlation name
                if (string.Compare(a.Alias, "Parent", true) == 0)
                    throw new Exception("internal restriction : " + a.Alias + " is an invalid correlation name");

                a.Source.EvaluateFrom(this);
                foreach (DataColumn dc in this.result.Columns)
                    dc.ColumnName = a.Alias + "." + dc.ColumnName;
                //this.result.TableName = a.Alias;
                AdoorLoger.Debug("Alias: " + a.Alias);
            }

            void IRelationalQueryEvaluator.EvaluateScan(ScanQuery f)
            {
                DataTable dt = this.context.GetTable(f.SourceName, f.DataColumns);
                //				this.result = this.dataset.Tables[f.SourceName];
                //				if (this.result == null) 
                //				{
                //					this.result = dt.Clone();
                //					this.result.PrimaryKey = null;
                //					this.dataset.Tables.Add(this.result);
                //				}
                if (this.result.Columns.Count != dt.Columns.Count)
                {
                    this.result.Columns.Clear();
                    foreach (DataColumn dc in dt.Columns)
                    {
                        DataColumn newdc = new DataColumn();
                        //newdc.ColumnMapping = dc.ColumnMapping;
                        //newdc.AllowDBNull = dc.AllowDBNull;
                        //newdc.AutoIncrement = dc.AutoIncrement;
                        //newdc.AutoIncrementSeed = dc.AutoIncrementSeed;
                        //newdc.AutoIncrementStep = dc.AutoIncrementStep;
                        //newdc.Caption = dc.Caption;
                        newdc.ColumnName = dc.ColumnName;
                        //newdc.Namespace = dc.Namespace;
                        newdc.DataType = dc.DataType;
                        //newdc.DefaultValue = dc.DefaultValue;
                        //newdc.ReadOnly = dc.ReadOnly;
                        //newdc.MaxLength = dc.MaxLength;
                        this.result.Columns.Add(newdc);
                    }
                }
                foreach (DataRow row in dt.Rows)
                    this.result.ImportRow(row);
                AdoorLoger.Debug("Scan " + f.SourceName + ": count = " + this.result.Rows.Count);
            }

            void IRelationalQueryEvaluator.EvaluateProject(ProjectQuery p)
            {
                p.Source.EvaluateFrom(this);

                // get the datacolumns matching p.Columns
                IDictionary<DataColumn, DataColumn> columns = new Dictionary<DataColumn, DataColumn>();
                foreach (SelectExpression sc in p.Columns)
                {
                    DataColumn dc = null;
                    string cn = sc.Alias;
                    ExpressionName expression = sc.Expr as ExpressionName;
                    if (expression == null)
                    {
                        string alias = sc.Alias;
                        dc = this.result.Columns[alias];
                        if (dc == null)
                            throw new Exception("expression too complex");

                    }
                    else
                    {
                        Debug.Assert(expression.Qualifier != null);
                        if (expression.Qualifier != "")
                        {
                            cn = expression.Qualifier + "." + expression.Value;
                            dc = this.result.Columns[cn];
                        }
                        else
                        {
                            // expression is not qualified (do we forbid this ?); try to match each this.result.Columns
                            cn = expression.Value;
                            foreach (DataColumn dc2 in this.result.Columns)
                            {
                                if (string.Compare(dc2.ColumnName.Substring(dc2.ColumnName.IndexOf(".") + 1), cn, true) == 0)
                                {
                                    if (dc != null)
                                        throw new Exception("Column " + expression + " is ambiguous");
                                    dc = dc2;
                                }
                            }
                        }
                    }
                    if (dc == null)
                        throw new Exception("column " + cn + " not found");
                    if (sc.Alias == null || sc.Alias == string.Empty)
                    {
                        if (sc.Expr is ExpressionName)
                            dc.ColumnName = ((ExpressionName)sc.Expr).Value;
                        else
                            throw new Exception(" Alias is required");
                    }
                    else
                        dc.ColumnName = sc.Alias;
                    columns.Add(dc, dc);
                }

                // get the datacolumns in this.result
                IList<DataColumn> resultcolumns = new List<DataColumn>();
                foreach (DataColumn dc in this.result.Columns)
                    resultcolumns.Add(dc);

                // remove datacolumns not in columns
                foreach (DataColumn dc in resultcolumns)
                    if (!columns.ContainsKey(dc))
                    {
                        AdoorLoger.Debug("DataSetEvaluator.EvaluateProject : remove " + dc.ColumnName);

                        this.result.Columns.Remove(dc);
                    }
                AdoorLoger.Debug("DataSetEvaluator.EvaluateProject : Columns.Count " + this.result.Columns.Count);
                AdoorLoger.Debug("DataSetEvaluator.EvaluateProject : Rows.Count " + this.result.Rows.Count);
                AdoorLoger.Debug("Project");
                
            }

            void IRelationalOperatorEvaluator.EvaluateSort(SortOperator s)
            {
                DataTable oldresult = this.result;
                this.result = Adoor.Data.DataUtils.CreateCaseInsesitiveDataTable();
                s.Source.EvaluateFrom(this);
                string sort = "";
                foreach (SortExpression so in s.SortOrder)
                {
                    if (sort != "")
                        sort += ", ";
                    if (!(so.Expr is ExpressionName))
                        throw new Exception("Expression too complex");

                    sort += ((ExpressionName)so.Expr).Qualifier + "." + ((ExpressionName)so.Expr).Value;
                    if (so.SortType == ScannerToken.Desc)
                        sort += " DESC";
                }

                DataView dv = new DataView(this.result);
                dv.Sort = sort;
                if (oldresult.Columns.Count == 0)
                {
                    foreach (DataColumn dc in this.result.Columns)
                    {
                        DataColumn newdc = new DataColumn();
                        newdc.ColumnMapping = dc.ColumnMapping;
                        newdc.AllowDBNull = dc.AllowDBNull;
                        newdc.AutoIncrement = dc.AutoIncrement;
                        newdc.AutoIncrementSeed = dc.AutoIncrementSeed;
                        newdc.AutoIncrementStep = dc.AutoIncrementStep;
                        newdc.Caption = dc.Caption;
                        newdc.ColumnName = dc.ColumnName;
                        newdc.Namespace = dc.Namespace;
                        newdc.DataType = dc.DataType;
                        newdc.DefaultValue = dc.DefaultValue;
                        newdc.ReadOnly = dc.ReadOnly;
                        newdc.MaxLength = dc.MaxLength;
                        oldresult.Columns.Add(newdc);
                    }
                }
                foreach (DataRowView drv in dv)
                    oldresult.ImportRow(drv.Row);
                this.result = oldresult;
                AdoorLoger.Debug("Sort");
                
            }

            void IRelationalOperatorEvaluator.EvaluateIntersect(IntersectOperator i)
            {
                throw new NotImplementedException();
            }

            void IRelationalQueryEvaluator.EvaluateUnion(UnionQuery u)
            {
                bool first = true;
                foreach (RelationalQuery exp in u.Sources)
                {
                    if (!first)
                    {
                        DataTable oldresult = this.result;

                        // evaluate in a temp table
                        this.result = Adoor.Data.DataUtils.CreateCaseInsesitiveDataTable();
                        exp.EvaluateFrom(this);
                        //					if (oldresult.Columns.Count == 0)
                        //					{
                        //						foreach(DataColumn dc in this.result.Columns)
                        //						{
                        //							DataColumn newdc = new DataColumn();
                        //							newdc.ColumnMapping = dc.ColumnMapping;
                        //							newdc.AllowDBNull = dc.AllowDBNull;
                        //							newdc.AutoIncrement = dc.AutoIncrement;
                        //							newdc.AutoIncrementSeed = dc.AutoIncrementSeed;
                        //							newdc.AutoIncrementStep = dc.AutoIncrementStep;
                        //							newdc.Caption = dc.Caption;
                        //							newdc.ColumnName = dc.ColumnName;
                        //							newdc.Namespace = dc.Namespace;
                        //							newdc.DataType = dc.DataType;
                        //							newdc.DefaultValue = dc.DefaultValue;
                        //							newdc.ReadOnly = dc.ReadOnly;
                        //							newdc.MaxLength = dc.MaxLength;
                        //							oldresult.Columns.Add(newdc);
                        //						}
                        //					}

                        // then merge with actual result
                        foreach (DataRow dr in this.result.Rows)
                            oldresult.ImportRow(dr);
                        this.result = oldresult;
                    }
                    else
                    {
                        exp.EvaluateFrom(this);
                        first = false;
                    }
                }
                AdoorLoger.Debug("Union: count = " + this.result.Rows.Count);
                
            }

            void IRelationalQueryEvaluator.EvaluateUnqualifier(UnqualifierQuery q)
            {
                q.Source.EvaluateFrom(this);
                UnqualifyColumnNames(this.result);
                AdoorLoger.Debug("Unqualifier");
                
            }

            protected IDataSetRelationalStore context;
            protected DataTable result;
        }

        public virtual IDataSetRelationalStore Clone(DataSet dataSet)
        {
            return new DataSetRelationalStore(dataSet);
        }

        
        #region IDisposable Members

        public void Dispose()
        {
            if (this.dataSet != null)
            {
                this.dataSet.Dispose();
                this.dataSet = null;
            }
        }

        #endregion
    }
}